package jwtx

import (
	"context"
	"errors"
	"fmt"
	"github.com/go-kratos/kratos/v2/middleware/auth/jwt"
	jwtv4 "github.com/golang-jwt/jwt/v4"
	"github.com/mitchellh/mapstructure"
	"time"
)

//// GenerateJwtTokenRegisterClaims 生成标准的token，使用RegisteredClaims，无需自己写key
//func GenerateJwtTokenRegisterClaims(key []byte, issuer string, userId int64) (string, error) {
//	registerClaims := jwtv4.RegisteredClaims{
//		//颁发者
//		Issuer: issuer,
//		//token办法时间
//		IssuedAt: &jwtv4.NumericDate{Time: time.Now()},
//		//过期时间
//		ExpiresAt: &jwtv4.NumericDate{Time: time.Now().AddDate(0, 0, 1)},
//		ID:        strconv.Itoa(int(userId)),
//	}
//	claims := jwtv4.NewWithClaims(jwtv4.SigningMethodES256, registerClaims)
//	return claims.SignedString(key)
//}

type RegisteredClaims[T any] struct {
	jwtv4.RegisteredClaims
	Payload T
}

type jwtToken[V any] struct {
	opt *options
	_   V
}

func NewJwtToken[V any](opts ...JwtTokenOptions) *jwtToken[V] {

	opt := &options{
		secretKey:     "",
		signingMethod: jwtv4.SigningMethodHS256,
	}

	for _, o := range opts {
		o(opt)
	}

	return &jwtToken[V]{opt: opt}
}

// GenerateToken claims 主要是外部传进来的用户信息等
func (t *jwtToken[V]) GenerateToken(claims V) (string, error) {

	var expiresAt *jwtv4.NumericDate
	if t.opt.expired > 0 {
		exp := time.Now().Add(t.opt.expired)
		expiresAt = jwtv4.NewNumericDate(exp)
	}
	issuedAt := jwtv4.NewNumericDate(time.Now())

	jwtClaims := RegisteredClaims[V]{
		RegisteredClaims: jwtv4.RegisteredClaims{
			Issuer:    t.opt.issuer,
			Audience:  []string{t.opt.audience},
			ExpiresAt: expiresAt,
			IssuedAt:  issuedAt,
		},
		Payload: claims,
	}
	token := jwtv4.NewWithClaims(t.opt.signingMethod, jwtClaims)
	return token.SignedString([]byte(t.opt.secretKey))
}

// ParseToken 解析给定的令牌并返回有效负载。如果令牌无效，则返回错误(用于解析和校验token，适用于自定义中间件处理)
func (t *jwtToken[V]) ParseToken(token string) (V, error) {
	claims := &RegisteredClaims[V]{}
	_, err := jwtv4.ParseWithClaims(token, claims, func(token *jwtv4.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwtv4.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method:%v", token.Header["alg"])
		}
		return []byte(t.opt.secretKey), nil
	})
	return claims.Payload, err
}

// ExtractClaimFromDefineClaim 在kratos框架中，已经把解析的claims放置到上下文ctx中，我们从jwt.FromContext(ctx)获取并进行类型断言即可
func ExtractClaimFromDefineClaim[V any](ctx context.Context) (*V, error) {
	claims, ok := jwt.FromContext(ctx)
	if !ok {
		return nil, errors.New("parse error")
	}
	//类型断言
	rClaims, ok := claims.(jwtv4.MapClaims)["Payload"]
	if !ok {
		return nil, errors.New("claims type assert error")
	}

	var payload V
	err := mapstructure.Decode(rClaims, &payload)
	if err != nil {
		return nil, err
	}
	return &payload, nil
}
